#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
改进报错信息
例如：
1+s 报错
1++s
   ^ invalid token


1++2 报错:
  .globl main
main:
   li a0, 1
1++2
  ^ expect a number

计算错误的代码行数；
只解决的了ASIC码，没有处理 汉字 unicode 等编码
*/

// 输入的字符串
static char *current_input;

typedef enum {
    TK_PUNCT, // 操作符如： + -
    TK_NUM,   // 数字
    TK_EOF,   // 文件终止符，即文件的最后
} token_kind;

// 标记符结构体
struct token{
  token_kind kind; // 类型
  struct token *next;    // 指向下一终结符
  int val;        // 值
  char *loc;      // 在解析的字符串内的位置
  int len;        // 长度
};

static void error(char* fmt, ...){
    // 封装一个异常函数
    va_list va;
    // 获取fmt后面的所有参数
    va_start(va, fmt);
    vfprintf(stderr, fmt, va);
    fprintf(stderr, "\n");
    va_end(va);
    exit(1);
}

// 输出错误出现的位置，并退出
static void verror_at(char *loc, char *fmt, va_list va) {
    // 先输出源信息
    fprintf(stderr, "%s\n", current_input);

    // 输出出错信息
    // 计算出错的位置，Loc是出错位置的指针，CurrentInput是当前输入的首地址
    int pos = loc - current_input;

    fprintf(stderr, "%*s", pos, "");

    fprintf(stderr, "^ ");
    vfprintf(stderr, fmt, va);
    fprintf(stderr, "\n");
    va_end(va);
    exit(1);
}

// 字符解析出错
static void error_at(char *loc, char *fmt, ...) {
  va_list va;
  va_start(va, fmt);
  verror_at(loc, fmt, va);
}

// Tok解析出错
static void error_tok(struct token *tok, char *fmt, ...) {
  va_list va;
  va_start(va, fmt);
  verror_at(tok->loc, fmt, va);
}

static int get_number(struct token* tk){
    if(tk->kind != TK_NUM){
        error_tok(tk, "expect a number");
    }
    return tk->val;
}

static bool equal(struct token* tok, char* str){
    // 比较字符串LHS（左部），RHS（右部）的前N位，S2的长度应大于等于N.
    // 比较按照字典序，LHS<RHS回负值，LHS=RHS返回0，LHS>RHS返回正值
    // 同时确保，此处的Op位数=N
    return memcmp(tok->loc, str, tok->len) == 0 && str[tok->len] == '\0';
}

static struct token *skip(struct token *tok, char *str) {
  if (!equal(tok, str))
    error_tok(tok, "expect '%s'", str);
  return tok->next;
}

// 生成新的Token
static struct token* new_token(token_kind tk, char* start, char* end) {
    // 分配1个Token的内存空间
    // 为什么只有申请内存没有释放内存呢？因为编译器通常是执行完就退出了
    // 这里借用操作系统在编译器执行结束后，会自动释放进程的内存，并且可以提高编译的速度
    struct token* tok = calloc(1, sizeof(struct token));
    tok->kind = tk;
    tok->loc = start;
    tok->len = end - start;
    return tok;
} 

// 终结符解析
struct token* tokenize(char* p){
    // 空的头指向队列
    struct token head = {};
    struct token* cur = &head;

    while(*p){
        // 跳过所有空白符如：空格、回车
        if(isspace(*p)){
            ++p;
            continue;
        }

        // 解析数字
        if(isdigit(*p)){
            // 我们不使用Head来存储信息，仅用来表示链表入口，这样每次都是存储在cur->next
            // 下述操作将使第一个token的地址不在head中。
            cur->next = new_token(TK_NUM, p, p);
            cur = cur->next;
            const char* _old = p;
            cur->val = strtol(p, &p, 10);
            cur->len = p - _old;
            continue;
        }
        // 解析操作符
        if(*p == '+' || *p == '-'){
            // 操作符的长度是1
            cur->next = new_token(TK_PUNCT, p, p+1);
            const char* _old = p;
            cur = cur->next;
            ++p;
            continue;
        }
        error_at(p, "invalid token");
    }
    // 解析结束，增加一个EOF，表示终止符。
    cur->next = new_token(TK_EOF, p, p);
    // Head无内容，所以直接返回Next
    return head.next;
}

int main(int argc, char **argv) {
    // 判断输入的参数是否为2个，argv[0]是程序名字
    // argv[1] 是输入的参数
    if (argc != 2){
        // 异常处理，提示参数不对
        error("%s: invalid number of arguments\n", argv[0]);
        // 程序返回值不为0时，表示存在错误
        return 1;
    }

    current_input = argv[1];

    // 解析Argv[1]
    struct token *tok = tokenize(argv[1]);

    printf("  .globl main\n");
    printf("main:\n");
    // 解析第一个数字
    // 假设我们的算是 num (op num) (op num) ...的形式
    printf("   li a0, %ld\n", get_number(tok));
    tok = tok->next;
    // 解析 (op num)
    while(tok->kind != TK_EOF) {
        // 解析 op
        if (equal(tok, "+")){
            tok = tok->next;
            // addi 中imm为有符号立即数，所以减法表示为rd = rs1 + (-imm)
            // addi 中imm最大啊为256 如果大于会溢出
            printf("   addi a0, a0, %ld\n", get_number(tok));
            tok = tok->next;
        } else if(equal(tok, "-")) {
            tok = skip(tok, "-");
            printf("   addi a0, a0, -%ld\n", get_number(tok));
            tok = tok->next;
        } else {
            fprintf(stderr, "unexpected character\n");
            return 1;
        }
    } 
    // ret为 jalr x0, x1 0别名指令，用于返回子程序
    printf("  ret\n");
    return 0;

}